## purpose: Model a member from the Complex numbers in its Cartesian form,
##          x + iy, and overload arithmetic and quote operators
## package: ComplexNumber
## author: Joel Dalley
## version: 2008/Nov/09
##
package ComplexNumber; {
    # Overload operators by specifying 
    # user defined functions in this package
    use overload (
        '+' => 'add', 
        '-' => 'subtract',
        '*' => 'multiply',
        '/' => 'divide',
        '""' => 'toString'
        );

    # ComplexNumber Constructor
    # param: string $class    the object type
    # param: float $x    the real part of a complex number
    # param: float $y    the imaginary part of a complex number
    sub new {
        my $class = shift || 'ComplexNumber';
        my $x = shift || 0;
        my $y = shift || 0;
        bless { 'Re' => $x, 'Im' => $y }, $class;
    }


    ### Getter Methods #########################
    
    # Getter for real part of this ComplexNumber
    sub real { (shift)->{Re}; }

    # Getter for imaginary part of this ComplexNumber
    sub imaginary { (shift)->{Im}; }

    # Computes and returns the polar coordinate 
    # radius, called the modulus, of this ComplexNumber
    sub modulus {
        # Object reference
        my $me = shift;

        # radius^2 = x^2 + y^2, so we compute x^2 and y^2
        my $xSquared = $me->real() ** 2;
        my $ySquared = $me->imaginary() ** 2;

        # Return radius = sqrt(x^2 + y^2)
        sqrt($xSquared + $ySquared);
    }

    # Computes and returns the polar coordinate
    # angle of this Complex number, in radians
    sub angle {
        my $me = shift;
        atan2($me->imaginary(), $me->real()); 
    }

    # Return string in format "(r, theta)"
    sub polarStr {
        my $me = shift;
        my $modulus = $me->printFormat($me->modulus());
        my $angle = $me->printFormat($me->angle());
        '(' . $modulus . ', ' . $angle . ')';
    }


    ### Overloaded Operator Methods ############
    

    # Return ComplexNumber as string in format: "x + iy"
    sub toString {
        my $me = shift;
        my $x = $me->printFormat($me->real());
        my $y = $me->printFormat($me->imaginary());
        $x . ' + i' . $y;
    }

    # Add two ComplexNumber objects together
    # and return the result as a ComplexNumber object
    sub add {
        # Store params and check that $z is a ComplexNumber
        my ($me, $z) = @_;
        $me->checkType($z);

        # Sum the real parts and imaginary parts separately
        my $x = $me->real() + $z->real();
        my $y = $me->imaginary() + $z->imaginary();

        # Return a new ComplexNumber with real $x and imaginary $y
        new ComplexNumber($x, $y);
    }

    # Subtract two ComplexNumber objects together
    # and return the result as a ComplexNumber object
    sub subtract {
        # Store params and check that $z is a ComplexNumber
        my ($me, $z) = @_;
        $me->checkType($z);

        # Subtract the real parts and imaginary parts separately
        my $x = $me->real() - $z->real();
        my $y = $me->imaginary() - $z->imaginary();

        # Return new Complex number with real $x and imaginary $y
        new ComplexNumber($x, $y);
    }

    # Multiply two ComplexNumber objects together
    # and return the result as a ComplexNumber object
    sub multiply {
        # Store params and check that $z is a ComplexNumber
        my ($me, $z) = @_;
        $me->checkType($z);

        # Store real and imaginary parts from both ComplexNumbers
        my $x1 = $me->real();
        my $y1 = $me->imaginary();
        my $x2 = $z->real();
        my $y2 = $z->imaginary();

        # Compute real and imaginary multiples
        my $x3 = $x1*$x2 - $y1*$y2;
        my $y3 = $y1*$x2 + $x1*$y2;

        # Return new ComplexNumber object
        new ComplexNumber($x3, $y3);
    }

    # Divide two ComplexNumber objects together
    # and return the result as a ComplexNumber object
    sub divide {
        # Store params and check that $z is a ComplexNumber
        my ($me, $z) = @_;
        $me->checkType($z);
        
        # Real and imaginary parts of $me and $z
        my $x1 = $me->real();
        my $y1 = $me->imaginary();
        my $x2 = $z->real();
        my $y2 = $z->imaginary();

        # Compute real and imaginary divisions
        my $x3 = ($x1*$x2 + $y1*$y2) / ($x2**2 + $y2**2);
        my $y3 = ($y1*$x2 - $x1*$y2) / ($x2**2 + $y2**2);

        # Return new ComplexNumber object
        new ComplexNumber($x3, $y3);
    }


    ### Helper Methods #########################
    

    # Dies if given argument, $z, isn't a ComplexNumber object
    sub checkType {
        my ($me, $z) = @_;
        UNIVERSAL::isa($z, 'ComplexNumber')  
            or die('ComplexNumber objects only');
    }

    # Specifies two digit decimal if the given number
    # isn't an integer; otherwise, just returns the integer
    sub printFormat {
        my ($me, $num) = @_;
        $num != int $num ? sprintf("%.2f", $num) : $num;
    }
}

1;
